/************************************************************
 * Copyright (c) 2010 Georg Fritzsche
 *
 * This project is licensed under the "New BSD License": 
 * http://www.opensource.org/licenses/bsd-license.php
 ***********************************************************/

#include "config.h"

#include <map>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdio>
#include <boost/program_options.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/case_conv.hpp>

#include "util.h"


namespace po = boost::program_options;

namespace
{
    po::options_description init_options_description()
    {
        po::options_description desc("Allowed options");
        
        desc.add_options()
          ("help,h", "produce help message.")
          ("list-devices,l", "list available midi ports and audio devices.")
          ("midi-port,P",       
             po::value<unsigned>()->default_value(0), 
             "midi port number.")
          ("audio-device,a", 
             po::value<int>()->default_value(0),
             "audio device number.")
          ("playlist,p",   
             po::value<std::string>()->default_value(std::string("./playlist.m3u")), 
             "path to playlist.")
          ("config,c",
             po::value<std::string>()->default_value(std::string("./trigger.cfg")), 
             "path to config file.")
          ("stream-samples,s", 
             po::value<bool>()->default_value(true),
             "if true, stream samples - otherwise load them into memory.") 
          ;
        
        return desc;
    }
    
    const po::options_description& options_description()
    {
        static const po::options_description desc = init_options_description();
        return desc;
    }
    
    MidiListener::Message parse_message_string(const std::string& in)
    {
        std::string s = in;
        boost::algorithm::trim(s);
        boost::algorithm::erase_all(s, " ");
        boost::algorithm::erase_all(s, "\t");
        boost::algorithm::to_lower(s);
        
        if ((s.length() % 2) != 0) {
            throw config_error("messages must be defined bytewise");
        }
        
        if (s.find_first_not_of("abcdef0123456789") != std::string::npos) {
            throw config_error("only hex chars and whitespace allowed in messages");
        }
        
        MidiListener::Message msg;
        
        for (size_t i=0; i<(s.length()-1); i+=2) {
            std::string t = s.substr(i, 2);
            unsigned char c = (unsigned char)std::strtoul(t.c_str(), 0, 16);
            msg.push_back(c);
        }
        
        return msg;
    }
    
    MidiListener::ActionMap parse_config_file(const std::string& path)
    {
        std::ifstream cfgFile(path.c_str(), std::ifstream::in);
        if (!cfgFile.is_open()) {
            throw config_error("could not open config file");
        }
        
        po::options_description desc("Allowed options");        
        desc.add_options()
          ("actions.next",       
             po::value<std::string>(), 
             "MIDI message for next sample")
          ("actions.reset",       
             po::value<std::string>(), 
             "MIDI message for resetting player")
          ;
        
        po::variables_map vm;
        po::store(po::parse_config_file(cfgFile, desc), vm);
        po::notify(vm);
        
#ifdef DEBUG
        dtrace("parsed config file: ");
        for (po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) {
            dtrace(" * " << it->first << ": " << it->second.as<std::string>());
        }
#endif
        
        MidiListener::ActionMap am = boost::assign::map_list_of
            (MidiListener::ActionNext , parse_message_string(vm["actions.next" ].as<std::string>()))
            (MidiListener::ActionReset, parse_message_string(vm["actions.reset"].as<std::string>()))
            ;
        
#ifdef DEBUG
        dtrace("\naction map: ");
        for (po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) {
            dtrace(" * " << it->first << ": " << it->second.as<std::string>());
        }
        dtrace("");
#endif
        
        return am;
    }

    po::variables_map parse_command_line(int argc, char** argv)
    {
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, options_description()), vm);
        po::notify(vm); 

        return vm;
    }
}

Config::Config(int argc, char** argv)
try
  : m_playlistPath()
  , m_midiPort()
  , m_exitNow(false)
  , m_listDevices(false)
  , m_streamSamples(true)
  , m_midiActionMap()
  , m_audioDeviceNumber()
{
    po::variables_map cmd_args = parse_command_line(argc, argv);
    
    if (cmd_args.count("help")) {
        std::cout << options_description() << std::endl;
        m_exitNow = true;
        return;
    }
    
    if (cmd_args.count("list-devices")) {
        m_listDevices = true;
        return;
    }
    
    m_playlistPath      = cmd_args["playlist"      ].as<std::string>();
    m_midiPort          = cmd_args["midi-port"     ].as<unsigned>();
    m_audioDeviceNumber = cmd_args["audio-device"  ].as<int>();
    m_streamSamples     = cmd_args["stream-samples"].as<bool>();

    const std::string configPath = cmd_args["config"].as<std::string>();

    tracei("config:"
        << "\n * playlist: "       <<  m_playlistPath 
        << "\n * config: "         <<  configPath
        << "\n * midi-port: "      <<  m_midiPort 
        << "\n * audio-device: "   <<  m_audioDeviceNumber
        << "\n * stream samples: " << (m_streamSamples ? "true" : "false")
        << "\n"
    );
    
    m_midiActionMap = parse_config_file(configPath);
}
catch (const po::error& e)
{
    throw config_error(e.what());
}
